package net.j4love.nio;

import org.junit.Test;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;

/**
 * @author He Peng
 * @create 2017-07-30 20:21
 * @update 2017-07-30 20:21
 * @updatedesc : 更新说明
 * @see
 */
public class ByteBufferTest {

    // ByteBuffer 会分配内存空间，在该空间中对数据进行读，写
    // 直接与非直接缓冲区 , 直接缓冲区Java虚拟机将尽力在其上直接执行本地I / O操作 ,
    // 它将尝试避免在每次调用其中一个底层操作系统的本机I / O操作之前（或之后）将缓冲区的内容复制到（或从）中间缓冲区。
    // 直接缓冲区节省了内存拷贝的操作
    // 直接缓冲区分配的内存通常比非直接缓冲区具有更高的分配和释放成本

    @Test
    public void allocate() throws Exception {
        // 在堆内存中分配缓冲区空间 java.nio.HeapByteBuffer
        // 超出 JVM 最大内存限制后抛出 java.lang.OutOfMemoryError: Java heap space
        // 因为使用的是 byte[] 分配内存
        ByteBuffer buf = ByteBuffer.allocate(1000000000);
        System.out.println(buf.toString());
    }

    @Test
    public void allocateDirect() throws Exception {
        // 在操作系统内存中分配缓冲区空间 java.nio.DirectByteBuffer
        // 分配空闲的大小不受 JVM 最大内存限制 ， 只和操作系统内存大小有关
        // 因为使用的是 sun.misc.Unsafe.allocateMemory() 分配内存
        ByteBuffer buf = ByteBuffer.allocateDirect(1800000000);
        if (buf.isDirect()) {
            System.out.println(buf.toString());
        }
        Thread.sleep(1000000);
    }

    @Test
    public void wrap() throws Exception {
        // java.nio.HeapByteBuffer
        ByteBuffer buf = ByteBuffer.wrap("春风十里不如你".getBytes("UTF-8"));
        System.out.println(buf.toString());
    }

    @Test
    public void write() throws Exception {
        ByteBuffer buf = ByteBuffer.allocate(100);
        buf.put("hello nio".getBytes());
        System.out.println(buf);
        if (buf.isReadOnly()) {
            System.out.println("buffer is read only");
        }
    }

    @Test
    public void read() throws Exception {
        ByteBuffer buf = ByteBuffer.allocate(15);
        buf.put("say hello".getBytes());
        System.out.println("read before : " + buf);
        byte[] dstBytes = new byte["say hello".length()];
        // 重置此缓冲区指针。将位置设置为 0 并丢弃标记
        // 不会改变 limit 的位置
//        buf.rewind();

        // 将指针指向 0 位置, limit 设置为当前 position 的位置 ,会改变 limit 的位置
        buf.flip();
        System.out.println("rewind after : " + buf);
        buf.get(dstBytes);
        System.out.println("geted data : " + new String(dstBytes));
    }

    @Test
    public void rewind() throws Exception {
        ByteBuffer buf = ByteBuffer.allocate(10);
        System.out.println("init bytebuffer : " + buf);
        buf.put("hello".getBytes());
        System.out.println("write after : " + buf);

        buf.rewind();   // rewind 方法不会改变 limit 的值
        System.out.println("rewind after : " + buf);
    }

    @Test
    public void filp() throws Exception {
        ByteBuffer buf = ByteBuffer.allocate(10);
        System.out.println("init bytebuffer : " + buf);
        buf.put("hello".getBytes());
        System.out.println("write after : " + buf);

        buf.flip(); // flip 方法会改变 limit 的值 ， 将当前 position 的值赋值给 limit
        System.out.println("flip after : " + buf);
    }

    @Test
    public void remaining() throws Exception {
        ByteBuffer buf = ByteBuffer.allocate(12);
        buf.put("hello".getBytes());
        System.out.println("write after : " + buf);
        System.out.println("invoke remaining return value = " + buf.remaining());
        // limit - position
    }


    @Test
    public void array() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(18);
        byteBuffer.put("hello world!".getBytes());
        byte[] bytes = byteBuffer.array();
        System.out.println(new String(bytes));
    }

    @Test
    public void compact() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(18);
        byteBuffer.put("hello world!".getBytes());
        System.out.println("compact before : " + byteBuffer);
        byteBuffer = byteBuffer.compact();
        System.out.println("compact after : " + byteBuffer);
    }

    @Test
    public void order() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(18);
        byteBuffer.put("hello world!".getBytes());
        ByteOrder byteOrder = byteBuffer.order();
        System.out.println(byteOrder);
    }

    // slice 会获取到一个新的 ByteBuffer ，capacity 是原来 ByteBuffer 剩余的字节数
    // 操作新的 ByteBuffer 原来的 ByteBuffer 不会受到影响
    @Test
    public void slice() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(18);
        byteBuffer.put("hello world!".getBytes());
        ByteBuffer byteBuffer1 = byteBuffer.slice();
        System.out.println("Old ByteBuffer == New ByteBuffer" + (byteBuffer == byteBuffer1));
        System.out.println("Old ByteBuffer :" + byteBuffer);
        System.out.println("New ByteBuffer :" + byteBuffer1);

        byteBuffer1.put("hello!".getBytes());
        System.out.println("New ByteBuffer write after Old ByteBuffer :" + byteBuffer);
        System.out.println("New ByteBuffer write after New ByteBuffer :" + byteBuffer1);
    }


    // duplicate 会获取到一个新的 ByteBuffer 是原有ByteBuffer 的副本
    // 操作新的 ByteBuffer 不会对原有 ByteBuffer 造成影响
    @Test
    public void duplicate() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(18);
        byteBuffer.put("hello world!".getBytes());
        ByteBuffer byteBuffer1 = byteBuffer.duplicate();
        System.out.println(byteBuffer == byteBuffer1);
        System.out.println("Old ByteBuffer :" + byteBuffer);
        System.out.println("New ByteBuffer :" + byteBuffer1);

        byteBuffer1.put("hello!".getBytes());
        System.out.println("New ByteBuffer write after Old ByteBuffer :" + byteBuffer);
        System.out.println("New ByteBuffer write after New ByteBuffer :" + byteBuffer1);
    }

    @Test
    public void basicsApiTest() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(20);
//        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(20);

        byteBuffer.put("hello nio".getBytes());
        System.out.println("Buffer put after -> " + byteBuffer);

        byteBuffer.flip();
        System.out.println("Buffer flip after -> " + byteBuffer);

        byte[] dst = new byte[byteBuffer.limit()];
        byteBuffer.get(dst);
        System.out.println("Buffer get after -> " + byteBuffer);
        System.out.println("read data -> " + new String(dst));

        byteBuffer.rewind();
        System.out.println("Buffer rewind after -> " + byteBuffer);
        byte[] dst1 = new byte[byteBuffer.limit()];
        byteBuffer.get(dst1);
        System.out.println("reread data -> " + new String(dst1));
        System.out.println("Buffer get after -> " + byteBuffer);

        System.out.println("Buffer remaining = " + byteBuffer.remaining());
        System.out.println("Buffer hasRemaining = " + byteBuffer.hasRemaining());

        System.out.println("Buffer clear after -> " + byteBuffer.clear());

        System.out.println("Buffer hasArray -> " + byteBuffer.hasArray());
        if (byteBuffer.hasArray()) {
            System.out.println("Buffer clear after array length -> " + byteBuffer.array().length);
            System.out.println("Buffer clear after array -> " + new String(byteBuffer.array()));
            byte[] array = byteBuffer.array();
            for (int i = 0 ; i < array.length ; i++) {
                array[i] = 0;
            }
            System.out.println("change byte array after -> " + new String(byteBuffer.array()));
            System.out.println("Buffer arrayOffset -> " + byteBuffer.arrayOffset());
        }
    }

    @Test
    public void allocateTest() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024 * 100);
        System.out.println("Buffer -> " + byteBuffer);
    }

    @Test
    public void allocateDirectTest() throws Exception {
        // Buffer 最大能分配 1G 内存空间
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 1024 * 1);
        System.out.println("Buffer -> " + byteBuffer);
    }

    @Test
    public void sliceTest() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(30);
//        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(30);

        byteBuffer.put("hello nio".getBytes());
        System.out.println("Buffer -> " + byteBuffer);

        // pos = 0 , lim = byteBuffer.remaining() , cap = byteBuffer.remaining() , off = this.position() + offset
        // 堆缓冲区共享同一个字节数组 , slice buffer 写入的数据不会覆盖原有 buffer 数据写入的数据，会追加在后面。
        // 但是原有的缓冲区修改后会覆盖新的缓冲区内容
        // 直接缓冲区共享同一内存地址空间
        ByteBuffer byteBuffer1 = byteBuffer.slice();
        System.out.println("slice buffer -> " + byteBuffer1);
        if (byteBuffer.hasArray()) {
            System.out.println("slice buffer array -> " + new String(byteBuffer1.array()));
            byteBuffer1.put("小猪佩奇".getBytes());
            System.out.println("slice buffer put after -> " + byteBuffer1);

            System.out.println("Buffer -> " + byteBuffer);
            System.out.println("Buffer array -> " + new String(byteBuffer.array()));
            byteBuffer.put("海军大将".getBytes());
            System.out.println("Buffer put after array -> " + new String(byteBuffer.array()));
            System.out.println("slice buffer array -> " + new String(byteBuffer1.array()));
        }

    }

    @Test
    public void duplicateTest() throws Exception {
        ByteBuffer byteBuffer = ByteBuffer.allocate(30);
        byteBuffer.put("hello nio".getBytes());
        System.out.println("Buffer -> " + byteBuffer);

        // 创建一个新的字节缓冲区，共享此缓冲区的内容。
        // this.markValue(),  this.position(), this.limit(), this.capacity()
        // 两个缓冲区共享同一内存空间 , 各自维护自己的 mark , position , limit , capacity
        ByteBuffer byteBuffer1 = byteBuffer.duplicate();
        System.out.println("duplicate buffer -> " + byteBuffer1);
        byteBuffer1.put("小猪佩奇".getBytes());
        System.out.println("duplicate buffer put after -> " + byteBuffer1);

        System.out.println("Buffer -> " + byteBuffer);
        System.out.println("Buffer array -> " + new String(byteBuffer.array()));
        byteBuffer.put(" 海军大将".getBytes());
        System.out.println("Buffer put after -> " + byteBuffer);
        System.out.println("Buffer put after array -> " + new String(byteBuffer.array()));

        System.out.println("duplicate buffer -> " + byteBuffer1);
        System.out.println("duplicate buffer array -> " + new String(byteBuffer1.array()));

    }

    @Test
    public void wrapTest() throws Exception {
        byte[] bytes = new byte[30];
        bytes[0] = 'H';
        bytes[1] = 'i';
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        System.out.println("buffer -> " + byteBuffer);
        System.out.println("buffer array -> " + new String(byteBuffer.array()));
    }

    @Test
    public void viewBufferTest() throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(30);
        buffer.put("hello nio".getBytes());
        System.out.println("buffer -> " + buffer);

        // 新缓冲区的内容将从此缓冲区的当前位置开始。 对这个缓冲区内容的更改将在新的缓冲区中可见，反之亦然;
        // 两个缓冲区的位置，极限和标记值将是独立的。
        // 新缓冲区的位置将为零，其容量和限制将是此缓冲区中剩余的字节数除以2，其标记将不定义。

        CharBuffer charBuffer = buffer.asCharBuffer();
        System.out.println("char buffer -> " + charBuffer.getClass().getName() + "[pos=" + charBuffer.position() + " lim=" + charBuffer.limit() + " cap=" + charBuffer.capacity() + "]");
        charBuffer.put(" ASCVFE");
        System.out.println("char buffer -> " + charBuffer.getClass().getName() + "[pos=" + charBuffer.position() + " lim=" + charBuffer.limit() + " cap=" + charBuffer.capacity() + "]");
        System.out.println("buffer -> " + buffer);
        System.out.println("buffer array -> " + new String(buffer.array()));

        buffer.put("-草莓派".getBytes());
        System.out.println("buffer put after -> " + buffer);
        System.out.println("buffer put after array -> " + new String(buffer.array()));

    }

    @Test
    public void getTest() throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(30);
        buffer.put("hello nio".getBytes());
        buffer.flip();
        // get() 读取 1 字节
        System.out.println("buffer get = " + (char)buffer.get());
        buffer.rewind();
        // getChar() 读取 2 字节
        System.out.println("buffer getChar = " + buffer.getChar());
        buffer.rewind();

        // getShort 读取 2 字节
        System.out.println("buffer getShort = " + buffer.getShort());
        buffer.rewind();

        // getInt 读取 4 字节
        System.out.println("buffer getInt = " + buffer.getInt());
        buffer.rewind();

        // getLong 读取 8 字节
        System.out.println("buffer getLong =" + buffer.getLong());
        buffer.rewind();

        // getFloat 读取 4 字节
        System.out.println("buffer getFloat = " + buffer.getFloat());
        buffer.rewind();

        // getDouble 读取 8 字节
        System.out.println("buffer getDouble = " + buffer.getDouble());

    }

    @Test
    public void putTest() throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(5);
        System.out.println("buffer -> " + buffer);
        buffer.put("Hi".getBytes());
        System.out.println("put after buffer -> " + buffer);
    }

    @Test
    public void get() throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(5);
        System.out.println("buffer -> " + buffer);
        buffer.put("Hi".getBytes());
        System.out.println("put after buffer -> " + buffer);
        buffer.flip();
        System.out.println("buffer flip after -> " + buffer);

//        byte[] dst = new byte[buffer.limit()];
//        buffer.get(dst);
//        System.out.println("get data = " + new String(dst));
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());

        System.out.println("get after buffer -> " + buffer);
    }


    @Test
    public void compactTest() throws Exception {
        ByteBuffer buffer = ByteBuffer.allocate(5);
        System.out.println("buffer -> " + buffer);
        buffer.put("HELLO".getBytes());
        System.out.println("put after buffer -> " + buffer);
        System.out.println("buffer array -> " + new String(buffer.array()));
        buffer.flip();
        buffer.get(new byte[3]);

        System.out.println("get after buffer -> " + buffer);


        // 作用是丢弃已读数据并可以继续往缓冲区内写入数据
        buffer.compact();
        System.out.println("buffer compact after -> " + buffer);
        System.out.println("buffer array -> " + new String(buffer.array()));
    }

    @Test
    public void equalsTest() throws Exception {
        ByteBuffer buffer0 = ByteBuffer.allocate(5);
        System.out.println("buffer0 -> " + buffer0);
        buffer0.put("HELLO".getBytes());
        System.out.println("buffer0 put after -> " + buffer0);

        buffer0.equals(null);
    }

    @Test
    public void orderTest() throws Exception {

        ByteOrder nativeOrder = ByteOrder.nativeOrder();
        System.out.println("native order = " + nativeOrder);
    }

}
